R kui kalkulaator
Kõige lihtsam viis R-i kasutada on temaga lihtsalt arvutusi teha.
5 + 7
[1] 12
25 ** 2
[1] 625
1 / 2
[1] 0.5
1 / 0
[1] Inf
0 / 0
[1] NaN
x = 5
ctrl alt i teeb uue koodi akna
shift enter paneb uue rea koodiblokist välja
ctrl enter jooksutab 1 rea
ctrl shift enter jooksutab terve koodibloki
Kohe on kasutajale kättesaadavad ka peamised matemaatilised
funktsioonid ja konstandid.
2 + sqrt(375769) - 25^2 # astendamismärgina võib kasutada nii ^ kui ka **, nt 2**3
[1] -10
sin(pi/6) + acosh(1)
[1] 0.5
log(exp(1))
[1] 1
sqrt(-1+0i)
[1] 0+1i
factorial(6) / choose(4, 2)
[1] 120
Kasutatud matemaatilised funktsioonid on näited R-s defineeritud
käskudest, mida kutsutakse välja nende nime ja järgnevate sulgudega.
Sulgude sisse lisatakse funktsiooni argumendid. Argumendi nimed võib,
kuid ei pea välja kirjutama, kui anda argumendi väärtused ette õiges
järjekorras. Näiteks järgnevad käsud annavad sama tulemuse.
log(x = 25, base = 5)
[1] 2
log(25, 5)
[1] 2
Abi R-s
Tihtipeale ei pea kõiki argumente määrama, sest nendel on määratud
vaikimisi väärtused. Selleks, et aru saada kuidas mõni funktsioon töötab
tasub vaadata konkreetse funktsiooni abi lehekülgi. Neile saab ligi
kirjutades funktsiooni ette ?. Näiteks.
?log
Abilehed tekivad kõrvalaknasse. Help failil on mitu standardset osa
millest on erinevate küsimuste puhul abi.
Usage: siin on funktsiooni väljakutse koos kõigi
parameetritega. Juhul kui konkreetsele parameetrile on defineeritud
vaikeväärtus, siis on see kirjas parameetri järel pärast
võrdusmärki.
Arguments: kirjeldab parameetri
tähendust.
Details: annab funktsiooni implementatsiooni
detailid.
Examples: näited mida üldiselt võib kohe läbi
jooksutada. Uue funktsiooni tundma õppimisel on näidete
sektsioon tihtipeale kõige suuremaks abiks, sest nii on
lihtsama saada aru sisendi ja väljundi oodatavast kujust.
Muutujad
Loomulikult nagu programmeerimisekeelele kohane saab R-s defineerida
ka muutujaid.
kaal = 80
pikkus = 180
bmi = kaal / (pikkus / 100)^2
bmi2 <- bmi**2
bmi
[1] 24.69136
jah = TRUE
ei = F
jah
[1] TRUE
ei
[1] FALSE
Muutujatele ei pea ette tüüpi, selle arvab R ise ära. Olulisemaid
muutuja tüüpe ei ole väga palju:
logical - tõeväärtuste hoidmiseks (väärtused
TRUE/FALSE),
numeric - kõik reaalarvud, sealhulgas
täisarvud,
character - sõned (sisestamisel panna jutumärkide
vahele).
Ühest tüübist teise teisendamiseks on mõeldud funktsioonid
as.<tüübi nimi>. Näiteks saab tihti veateateid, kui
tehteid tehakse valest tüübist muutujatega.
"5" + 5
Error in "5" + 5 : non-numeric argument to binary operator
Kasutades tüübiteisendusi saab neist üle.
as.numeric("5") + 5
[1] 10
Ülesanne 2
TRUE + TRUE
[1] 2
TRUE - TRUE
[1] 0
TRUE - FALSE
[1] 1
F - T
[1] -1
FALSE + FALSE
[1] 0
FALSE - FALSE
[1] 0
Funktsioonide defineerimine
Me enne nägime, et on võimalik kasutada ette antud funktsioone. Kuid
neid on ka lihtne defineerida järgneva süntaksiga.
<funktsiooni nimi> = function(<argumentide nimekiri>){
<argumentidega tehtavad operatsioonid>
return(<tagastatav väärtus>)
}
Näiteks võime defineerida kehamassi indeksi arvutamise
funktsiooni.
bmi = function(kaal, pikkus = 180){
res = kaal / (pikkus / 100) ** 2
return(res)
}
bmi(85)
[1] 26.23457
bmi(85, 195)
[1] 22.35371
Lisapaketid
Paljud R-i lisafunktsioonid on pakendatud pakettidesse, mis tuleb
kasutamiseks eraldi laadida käsuga library. Näiteks
funktsioon as_date on lisapaketis lubridate,
mille peab enne kasutamist sisse lugema.
#as_date("2012-01-01")
library(lubridate)
as_date("2012-01-01")
[1] "2012-01-01"
Enamus pakette mis asuvad keskses repositooriumis CRAN-s, saab R-s
installida kas käsuga install.packages konsoolilt või
Packages saki alt paremal alumises aknas RStudios (kui vaja valige
Install from: Repository (CRAN)).
Ülesanne 3
- Installi pakett
tidyverse ja loe see sisse.
library(tidyverse)
Vektorid
Keskne objektitüüp R-s on vektor (sarnane 1D
numpy array-ga). mis on teisisõnu ühe mõõtmeline sama tüüpi
muutujate järjestus. Vektorite loomiseks on mitmeid viise.
1:10 # Järjest numbrid
[1] 1 2 3 4 5 6 7 8 9 10
9:2 # Tagurpidi järjestus
[1] 9 8 7 6 5 4 3 2
c(1, 4, 2, 6) # Suvaliste elementide järjestus
[1] 1 4 2 6
c(1:10, 4, c(2, 4)) # Argumendid võivad olla nii vektori kui üksikud väärtused
[1] 1 2 3 4 5 6 7 8 9 10 4 2 4
c("A", "B", "C") # Vektorisse võib panna ka sõnesid
[1] "A" "B" "C"
seq(0, 1, length.out = 10) # Suvalise algus- ja lõpppunktiga võrdse vahemikuga jadad
[1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667 0.7777778 0.8888889 1.0000000
rep(1:2, times = 5)
[1] 1 2 1 2 1 2 1 2 1 2
fnc “c” tähendab combine!!! c(1, 2) loob 1d array (vektori) [1,
2]
Andmete eraldamine vektorist käib kandiliste sulgudega. Tasub meelde
jätta et R-s algab indekseerimine 1-st.
x = 10:1
x[1]
[1] 10
x[1:5]
[1] 10 9 8 7 6
Tegelikult käsitletakse R-s isegi ühe väärtusega objekte vektoritena.
Seega pole suurt vahet kas tehetes on vektor või üksikud väärtused.
Vektori puhul tehakse tehteid elemendi kaupa. Kui sisendiks on erineva
pikkusega vektorid siis lühemat korratakse nii kaua kui saaab pikemaga
võrdseks. Sama loogika kehtib nii aritmeetiliste tehete kui ka paljude
funktsioonide puhul.
bmi(c(85, 90, 95, 100, 105), 194)
[1] 22.58476 23.91327 25.24179 26.57031 27.89882
Paljude funktsioonide puhul muidugi oodataksegi vektorit ja
tulemuseks on arv.
min(1:10)
[1] 1
max(1:10)
[1] 10
mean(1:10)
[1] 5.5
median(0:10)
[1] 5
Ülesanne 4
Defineeri funktsioon mis teisendab etteantud numbrilise vektori 0
ja 1 vahele, nii et minimaalne väärtus on 0 ja maksimaalne 1. Vihje:
vektori x ja selle elemendi xi korral on valem
järgmine (xi - min(x))/(max(x) -min(x)).
Testi saadud funktsiooni, vektorite 0:10 ja
-5:5 peal. Kas tulemused on sarnased või erinevad?
vektor_teisenda = function(vektor) {
vastus =(vektor - min(vektor)) / (max(vektor) - min(vektor))
return(vastus)
}
vektor_teisenda(0:10)
[1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Andmetabelite töötlemine tidyverse käskudega
R-s on mitmeid viise kuidas andmeid saab töödelda, viimasel ajal on
väga populaarseks saanud lähenemine mis on implementeeritud pakettide
kogumikus ühise nimetajaga tidyverse. Võiks öelda, et tegu
on lausa omamoodi alamkeelega R-i sees, mis laenab kontseptsioone
erinveatest keeltest nagu SQL ja bash ning
proovib paljud operatsioonid panna tööle ühiste printsiipide põhjal.
%>%
Üheks tähtsamaks käsuks tidyverse puhul on nn “toru”
%>% mis võimaldab kirjutada pikkasid käskude jadasid
loetavalt. Illustreerimaks selle kasulikkust vaatame järgmist
näidet.
prices = c("$1423.55", "$556.98", "$4321.99", "$657.01")
prices_trim = str_replace(prices, "\\$", "")
prices_trim_num = as.numeric(prices_trim)
prices_trim_num_round = round(prices_trim_num, digits = 0)
prices_round_final = str_c("$", prices_trim_num_round)
prices_round_final
[1] "$1424" "$557" "$4322" "$657"
Siin rakendame järjest mitmeid operatsioone ja salvestame kõik
vahemuutujatesse millele peame nimesid mõtlema, mis on suhteliselt
tüütu. Me võime sellise operatsioooni kirjutada ka ühel real.
str_c("$", round(as.numeric(str_replace(prices, "\\$", "")), digits = 0))
[1] "$1424" "$557" "$4322" "$657"
Kuid sellist asja on väga raske lugeda. Sest funktsioone rakendatakse
järjest seestpooolt väljapoole. Palju lihtsam oleks lugeda, kui
funktsioonid liiguks rakendamise järjekorras vasakult paremale. See on
just see mida operaator %>% teeb. Ta võimaldab kirjutada
f(g(x)), kui x %>% g() %>% f(). Meie
eelmine näide näeks välja järgnev.
prices %>% str_replace("\\$", "") %>% as.numeric() %>% round() %>% str_c("$", .)
[1] "$1424" "$557" "$4322" "$657"
Pane tähele, et kui eelneva funktsiooni väljund peaks olema järgmises
funktsiooni väljakutses esimesel positsioonil, siis võib selle ära
jätta. Kui väljund peab minema mõnele teisele positsioonile, siis saab
seda märkida kui . (vt viimane käsk).
Tidyverse funktsioonid
Tidyverse on üles ehitatud funktsioonidel, millest igaüks võtab sisse
andmetabeli ja annab välja modifitseeritud andmetabeli. Iga funktsioon
teostab ainult ühte operatsiooni, aga kui neid operaatoriga %>%
järjest rakendada on võimalik saavutada väga palju. Peamised
funktsioonid tidyverse pakettides on järgnevad:
select - võimaldab valida andmetabeli
veerge
filter - võimaldab valida andmetabeli
ridu
mutate - võimaldab tekitada tabelisse uusi
veergusid või modifitseerida vanu
group_by ja summarize -
võimaldavad summeerida väärtusi tunnuste poolt defineeritud
gruppides
arrange - võimaldab sorteerida tabelit ühe või
mitme tunnuse järgi
Järgnevalt vaatame iga funktsiooni ja mõnda kasutusnäidet täpsemalt
kasutades andmestikku mass, mis sai sisse loetud
eelnevalt.
select
select võimaldab andmetabeli veerge valida ja neid ka
selle käigus ümber nimetada. Veergude nimed saab funktsioonile ette anda
ilma jutumärkideta.
mass %>% select(AGEP, SEX)
mass %>% select(Age = AGEP, Gender = SEX) # paneme ilusamad nimed
mass %>% select(-CIT, -AGEP) # Saame ka konkreetseid veerge välja visata
filter
filter võimaldab ridu filtreerida seades loogilisi
tingimusi veergudele. Sisendtabelis olevate veergude nimed tunneb
filter automaatselt ära.
mass %>% filter(AGEP < 20) # Vanus väiksem kui 20
mass %>% filter(CIT == "Not a citizen of the U.S.")
mass %>% filter(
CIT
%in%
c(
"U.S. citizen by naturalization",
"Not a citizen of the U.S."
)
)
mass %>% filter(AGEP < 20 | CIT == "Not a citizen of the U.S.")
mutate
mutate võimaldab luua uusi veeraid vastavalt sellele kas
veerg millele väärtus omistatakse juba eksiteerib või mitte. Nagu
eelnevatelgi funktsioge või muuta olemasolevonidel mutate
sees saab tehetel kasutada veerunimesid.
mass %>% mutate(Old = AGEP > 60) %>% select(AGEP, Old)
mass %>% mutate(AGEP = AGEP + 1)
summarize
summarize on käsk mis võimaldab andmestikul kokkuvõtteid
välja arvutada. Erinevalt mutate käsust tagastab ta ainult
väljaarvutatud suurused ja mitte midagi muud.
mass %>% summarize(MeanAge = mean(AGEP))
mass %>% summarize(MeanAge = mean(AGEP), MinAge = min(AGEP))
Funktsioon n() summarize sees ütleb kui
mitu rida sisend tabelis on.
mass %>% summarize(MeanAge = mean(AGEP), MinAge = min(AGEP), N = n())
mass %>%
summarize(
MeanAge = mean(AGEP),
MinAge = min(AGEP),
MaxAge = max(AGEP),
N = n()
);
group_by
group_by võimaldab rakendada nn
“split-apply-combine”strateegiat, kus andmestik tükeldatakse
ühe või mitme tunnuse väärtus alusel alamandmestikeks, rakendatakse
mingit funktsiooni alam-andmestikel ning tulemused kombineeritakse. Kui
me oleme rakendanud käsku group_by andmestikule siis järgnevate käskude
puhul just selline operatsioon toimubki.
group_by ja summarize moodustavad
kombinatsiooni, millega on võimalik tunnuste poolt defineeritud gruppide
kaupa summeerida teiste muutujate väärtuseid. Tulemusse jäävad alles
tunnused mille järgi grupeeriti ning summarize käsus
defineeeritud tunnused. Grupeerida võib nii ühe kui mitme tunnuse
järgi.
mass %>% group_by(CIT) %>% summarize(AGEP = mean(AGEP))
mass %>% group_by(CIT, SEX) %>% summarize(AGEP = mean(AGEP))
Funktsioon n() summarize sees ütleb kui
mitu tabeli rida konkreetsele grupeerivate tunnuste kombinatsioonile
vastab. See on väga kasulik sagedustabelite tegemisel
mass %>% group_by(CIT) %>% summarize(N = n())
mass %>% group_by(CIT, SEX) %>% summarize(AGEP = mean(AGEP), N = n())
Kui pärast group_by käsku rakenda mutate käsku. Siis mutate töötab
jupikaupa group_by defineeritud alamhulkadel. Näiteks nii saame lisada
igale reale grupikeskmise või järjekorra numbri grupi sees.
mass %>% select(AGEP, SEX) %>% group_by(SEX) %>% mutate(MeanAgeGroup = mean(AGEP))
mass %>% select(AGEP, SEX) %>% group_by(SEX) %>% mutate(IdInGroup = 1:n())
arrange
arrange lihtsalt sorteerib andmetabeli etteantud
tunnus(t)e järgi.
mass %>% arrange(AGEP) # Vaikimise väiksemast suuremaks
mass %>% select(AGEP, SEX) %>% arrange(AGEP, SEX)
# mass %>% arrange(desc(AGEP)) # Käsuga desc saab sorteerimise suuna ümber pöörata
mass %>% arrange(-AGEP) # Käsuga desc saab sorteerimise suuna ümber pöörata
Funktsioonide kombineerimine
Olgugi, et iga funktsioon üksi teostab konkreetse lihtsa
operatsiooni, võib neid üksteise järel ritta ladudes lahendada päris
keerukaid probleeme. Näiteks siin leiame top 5 ametit tööealistele USAs
ja väljaspool sündinud inimestele.
mass %>%
select(Age = AGEP, Citizenship = CIT, Occupation = OCCP) %>%
filter(Age > 18 & Age < 65) %>%
mutate(BornUS = Citizenship == "Born in the U.S.") %>%
group_by(BornUS, Occupation) %>%
summarize(N = n()) %>%
group_by(BornUS) %>%
mutate(Rank = rank(-N)) %>%
filter(Rank <= 5) %>%
arrange(BornUS, Rank)
Ülesanded 6
Kummal on kõrgem keskmine palk kas üle või alla 55 aastastel
arstidel? (Kasuta tunnuseid WAGP - palk, AGEP - vanus, OCCP - amet,
kategooria “MED-PHYSICIANS AND SURGEONS”, W)
Millise ameteid esindavad naised töötavad keskmiselt nädalas
kõige kauem? Keskendu ainult ametitele, mida esindavaid naisi on
andmestikus vähemal 10. (Kasuta tunnuseid AGEP - vanus, SEX - sugu
väärtus “Female” , WKHP - nädalas tehtud töötunnid, OCCP -
amet)
Mis on täiskasvanud inimeste kõige kõrgema keskmise palgaga
ametid Massachusettsi andmestiku kohaselt? Keskendu ainult ametitele,
mille esindajaid on andmestikus vähemal 10. (Kasuta tunnuseid AGEP -
vanus, WAGP - palk, OCCP - amet)
mass %>%
select(Age = AGEP, Occupation = OCCP, Wage = WAGP) %>%
filter(Occupation == "MED-PHYSICIANS AND SURGEONS") %>%
mutate(isOver55 = Age >= 55) %>%
group_by(isOver55, Occupation) %>%
summarise(MeanWage = mean(Wage))
mass %>%
select(sex = SEX, occupation = OCCP, workHoursPerWeek = WKHP) %>%
filter(sex == "Female") %>%
group_by(occupation) %>%
filter(n() >= 10) %>%
summarise(meanHours = mean(workHoursPerWeek)) %>% # mean(workHoursPerWeek, na.rm = TRUE)
arrange(-meanHours) %>%
select(occupation, meanHours)
mass %>%
filter(SEX == "Female") %>%
group_by(OCCP) %>%
filter(n() >= 10) %>%
summarise(
mean_hours = mean(WKHP, na.rm = TRUE),
count = n()
) %>%
arrange(desc(mean_hours))
LS0tDQp0aXRsZTogIlByYWt0aWt1bSA1IC0gUiBhbHVzZWQiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyBSIGt1aSBrYWxrdWxhYXRvcg0KDQpLw7VpZ2UgbGlodHNhbSB2aWlzIFItaSBrYXN1dGFkYSBvbiB0ZW1hZ2EgbGlodHNhbHQgYXJ2dXR1c2kgdGVoYS4NCg0KYGBge3J9DQo1ICsgNyANCjI1ICoqIDIgDQoxIC8gMg0KMSAvIDAgDQowIC8gMA0KDQp4ID0gNQ0KYGBgDQoNCmBgYHtyfQ0KDQpgYGANCg0KY3RybCBhbHQgaSB0ZWViIHV1ZSBrb29kaSBha25hDQoNCnNoaWZ0IGVudGVyIHBhbmViIHV1ZSByZWEga29vZGlibG9raXN0IHbDpGxqYQ0KDQpjdHJsIGVudGVyIGpvb2tzdXRhYiAxIHJlYQ0KDQpjdHJsIHNoaWZ0IGVudGVyIGpvb2tzdXRhYiB0ZXJ2ZSBrb29kaWJsb2tpDQoNCktvaGUgb24ga2FzdXRhamFsZSBrw6R0dGVzYWFkYXZhZCBrYSBwZWFtaXNlZCBtYXRlbWFhdGlsaXNlZCBmdW5rdHNpb29uaWQgamEga29uc3RhbmRpZC4NCg0KYGBge3J9DQoyICsgc3FydCgzNzU3NjkpIC0gMjVeMiAjIGFzdGVuZGFtaXNtw6RyZ2luYSB2w7VpYiBrYXN1dGFkYSBuaWkgXiBrdWkga2EgKiosIG50IDIqKjMgDQpzaW4ocGkvNikgKyBhY29zaCgxKSANCmxvZyhleHAoMSkpIA0Kc3FydCgtMSswaSkgDQpmYWN0b3JpYWwoNikgLyBjaG9vc2UoNCwgMikgDQpgYGANCg0KS2FzdXRhdHVkIG1hdGVtYWF0aWxpc2VkIGZ1bmt0c2lvb25pZCBvbiBuw6RpdGVkIFItcyBkZWZpbmVlcml0dWQga8Okc2t1ZGVzdCwgbWlkYSBrdXRzdXRha3NlIHbDpGxqYSBuZW5kZSBuaW1lIGphIGrDpHJnbmV2YXRlIHN1bGd1ZGVnYS4gU3VsZ3VkZSBzaXNzZSBsaXNhdGFrc2UgZnVua3RzaW9vbmkgYXJndW1lbmRpZC4gQXJndW1lbmRpIG5pbWVkIHbDtWliLCBrdWlkIGVpIHBlYSB2w6RsamEga2lyanV0YW1hLCBrdWkgYW5kYSBhcmd1bWVuZGkgdsOkw6RydHVzZWQgZXR0ZSDDtWlnZXMgasOkcmpla29ycmFzLiBOw6RpdGVrcyBqw6RyZ25ldmFkIGvDpHN1ZCBhbm5hdmFkIHNhbWEgdHVsZW11c2UuDQoNCmBgYHtyfQ0KbG9nKHggPSAyNSwgYmFzZSA9IDUpIA0KbG9nKDI1LCA1KSANCmBgYA0KDQojIyBBYmkgUi1zDQoNClRpaHRpcGVhbGUgZWkgcGVhIGvDtWlraSBhcmd1bWVudGUgbcOkw6RyYW1hLCBzZXN0IG5lbmRlbCBvbiBtw6TDpHJhdHVkIHZhaWtpbWlzaSB2w6TDpHJ0dXNlZC4gU2VsbGVrcywgZXQgYXJ1IHNhYWRhIGt1aWRhcyBtw7VuaSBmdW5rdHNpb29uIHTDtsO2dGFiIHRhc3ViIHZhYWRhdGEga29ua3JlZXRzZSBmdW5rdHNpb29uaSBhYmkgbGVoZWvDvGxnaS4gTmVpbGUgc2FhYiBsaWdpIGtpcmp1dGFkZXMgZnVua3RzaW9vbmkgZXR0ZSBgP2AuIE7DpGl0ZWtzLg0KDQpgYGB7cn0NCj9sb2cNCmBgYA0KDQpBYmlsZWhlZCB0ZWtpdmFkIGvDtXJ2YWxha25hc3NlLiBIZWxwIGZhaWxpbCBvbiBtaXR1IHN0YW5kYXJkc2V0IG9zYSBtaWxsZXN0IG9uIGVyaW5ldmF0ZSBrw7xzaW11c3RlIHB1aHVsIGFiaS4NCg0KLSAgICoqVXNhZ2U6Kiogc2lpbiBvbiBmdW5rdHNpb29uaSB2w6RsamFrdXRzZSBrb29zIGvDtWlnaSBwYXJhbWVldHJpdGVnYS4gSnVodWwga3VpIGtvbmtyZWV0c2VsZSBwYXJhbWVldHJpbGUgb24gZGVmaW5lZXJpdHVkIHZhaWtldsOkw6RydHVzLCBzaWlzIG9uIHNlZSBraXJqYXMgcGFyYW1lZXRyaSBqw6RyZWwgcMOkcmFzdCB2w7VyZHVzbcOkcmtpLg0KDQotICAgKipBcmd1bWVudHM6Kioga2lyamVsZGFiIHBhcmFtZWV0cmkgdMOkaGVuZHVzdC4NCg0KLSAgICoqRGV0YWlsczoqKiBhbm5hYiBmdW5rdHNpb29uaSBpbXBsZW1lbnRhdHNpb29uaSBkZXRhaWxpZC4NCg0KLSAgICoqRXhhbXBsZXM6KiogbsOkaXRlZCBtaWRhIMO8bGRpc2VsdCB2w7VpYiBrb2hlIGzDpGJpIGpvb2tzdXRhZGEuICoqVXVlIGZ1bmt0c2lvb25pIHR1bmRtYSDDtXBwaW1pc2VsIG9uIG7DpGlkZXRlIHNla3RzaW9vbiB0aWh0aXBlYWxlIGvDtWlnZSBzdXVyZW1ha3MgYWJpa3MqKiwgc2VzdCBuaWkgb24gbGlodHNhbWEgc2FhZGEgYXJ1IHNpc2VuZGkgamEgdsOkbGp1bmRpIG9vZGF0YXZhc3Qga3VqdXN0Lg0KDQojIyMgw5xsZXNhbm5lIDENCg0KLSAgIE1pZGEgdGVlYiBmdW5rdHNpb29uIGBybWA/DQoNCi0gICBQcm9vdmkgbMOkYmkgw7xrcyBuw6RpZGUgZnVua3RzaW9vbmlzdCBgcm1gPw0KDQpgYGB7cn0NCj9ybQ0KDQpgYGANCg0KYGBge3J9DQpybSh4KQ0KYGBgDQoNCiMjIE11dXR1amFkDQoNCkxvb211bGlrdWx0IG5hZ3UgcHJvZ3JhbW1lZXJpbWlzZWtlZWxlbGUga29oYW5lIHNhYWIgUi1zIGRlZmluZWVyaWRhIGthIG11dXR1amFpZC4NCg0KYGBge3J9DQprYWFsID0gODAgDQpwaWtrdXMgPSAxODANCmJtaSA9IGthYWwgLyAocGlra3VzIC8gMTAwKV4yIA0KDQpibWkyIDwtIGJtaSoqMg0KDQpibWkNCg0KamFoID0gVFJVRQ0KZWkgPSBGDQoNCmphaA0KZWkNCmBgYA0KDQpNdXV0dWphdGVsZSBlaSBwZWEgZXR0ZSB0w7zDvHBpLCBzZWxsZSBhcnZhYiBSIGlzZSDDpHJhLiBPbHVsaXNlbWFpZCBtdXV0dWphIHTDvMO8cGUgZWkgb2xlIHbDpGdhIHBhbGp1Og0KDQotICAgYGxvZ2ljYWxgIC0gdMO1ZXbDpMOkcnR1c3RlIGhvaWRtaXNla3MgKHbDpMOkcnR1c2VkIGBUUlVFYC9gRkFMU0VgKSwNCg0KLSAgIGBudW1lcmljYCAtIGvDtWlrIHJlYWFsYXJ2dWQsIHNlYWxodWxnYXMgdMOkaXNhcnZ1ZCwNCg0KLSAgIGBjaGFyYWN0ZXJgIC0gc8O1bmVkIChzaXNlc3RhbWlzZWwgcGFubmEganV0dW3DpHJraWRlIHZhaGVsZSkuDQoNCsOcaGVzdCB0w7zDvGJpc3QgdGVpc2UgdGVpc2VuZGFtaXNla3Mgb24gbcO1ZWxkdWQgZnVua3RzaW9vbmlkIGBhcy48dMO8w7xiaSBuaW1pPmAuIE7DpGl0ZWtzIHNhYWIgdGlodGkgdmVhdGVhdGVpZCwga3VpIHRlaHRlaWQgdGVoYWtzZSB2YWxlc3QgdMO8w7xiaXN0IG11dXR1amF0ZWdhLg0KDQpgYGB7cn0NCiI1IiArIDUNCmBgYA0KDQpLYXN1dGFkZXMgdMO8w7xiaXRlaXNlbmR1c2kgc2FhYiBuZWlzdCDDvGxlLg0KDQpgYGB7cn0NCmFzLm51bWVyaWMoIjUiKSArIDUNCmBgYA0KDQojIyMgw5xsZXNhbm5lIDINCg0KLSAgIEthcyB0w7VldsOkw6RydHVzaSBzYWFiIGxpaXRhPw0KDQotICAgTWlsbGluZSBvbiBUUlVFIG51bWJyaWxpbmUgdsOkw6RydHVzPw0KDQpgYGB7cn0NClRSVUUgKyBUUlVFDQpUUlVFIC0gVFJVRQ0KDQpUUlVFIC0gRkFMU0UNCkYgLSBUDQoNCkZBTFNFICsgRkFMU0UNCkZBTFNFIC0gRkFMU0UNCmBgYA0KDQojIyBGdW5rdHNpb29uaWRlIGRlZmluZWVyaW1pbmUNCg0KTWUgZW5uZSBuw6RnaW1lLCBldCBvbiB2w7VpbWFsaWsga2FzdXRhZGEgZXR0ZSBhbnR1ZCBmdW5rdHNpb29uZS4gS3VpZCBuZWlkIG9uIGthIGxpaHRuZSBkZWZpbmVlcmlkYSBqw6RyZ25ldmEgc8O8bnRha3NpZ2EuDQoNCmBgYCByDQo8ZnVua3RzaW9vbmkgbmltaT4gPSBmdW5jdGlvbig8YXJndW1lbnRpZGUgbmltZWtpcmk+KXsgDQogICAgIDxhcmd1bWVudGlkZWdhIHRlaHRhdmFkIG9wZXJhdHNpb29uaWQ+IA0KICAgICByZXR1cm4oPHRhZ2FzdGF0YXYgdsOkw6RydHVzPikgDQp9IA0KYGBgDQoNCk7DpGl0ZWtzIHbDtWltZSBkZWZpbmVlcmlkYSBrZWhhbWFzc2kgaW5kZWtzaSBhcnZ1dGFtaXNlIGZ1bmt0c2lvb25pLg0KDQpgYGB7cn0NCmJtaSA9IGZ1bmN0aW9uKGthYWwsIHBpa2t1cyA9IDE4MCl7DQogIHJlcyA9IGthYWwgLyAocGlra3VzIC8gMTAwKSAqKiAyDQogIA0KICByZXR1cm4ocmVzKQ0KfQ0KDQpibWkoODUpDQpibWkoODUsIDE5NSkNCmBgYA0KDQojIyBMaXNhcGFrZXRpZA0KDQpQYWxqdWQgUi1pIGxpc2FmdW5rdHNpb29uaWQgb24gcGFrZW5kYXR1ZCBwYWtldHRpZGVzc2UsIG1pcyB0dWxlYiBrYXN1dGFtaXNla3MgZXJhbGRpIGxhYWRpZGEga8Okc3VnYSBgbGlicmFyeWAuIE7DpGl0ZWtzIGZ1bmt0c2lvb24gYGFzX2RhdGVgIG9uIGxpc2FwYWtldGlzIGBsdWJyaWRhdGVgLCBtaWxsZSBwZWFiIGVubmUga2FzdXRhbWlzdCBzaXNzZSBsdWdlbWEuDQoNCmBgYHtyfQ0KI2FzX2RhdGUoIjIwMTItMDEtMDEiKQ0KDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgDQoNCmFzX2RhdGUoIjIwMTItMDEtMDEiKQ0KYGBgDQoNCkVuYW11cyBwYWtldHRlIG1pcyBhc3V2YWQga2Vza3NlcyByZXBvc2l0b29yaXVtaXMgQ1JBTi1zLCBzYWFiIFItcyBpbnN0YWxsaWRhIGthcyBrw6RzdWdhIGBpbnN0YWxsLnBhY2thZ2VzYCBrb25zb29saWx0IHbDtWkgUGFja2FnZXMgc2FraSBhbHQgcGFyZW1hbCBhbHVtaXNlcyBha25hcyBSU3R1ZGlvcyAoa3VpIHZhamEgdmFsaWdlIEluc3RhbGwgZnJvbTogUmVwb3NpdG9yeSAoQ1JBTikpLg0KDQojIyMgw5xsZXNhbm5lIDMNCg0KLSAgIEluc3RhbGxpIHBha2V0dCBgdGlkeXZlcnNlYCBqYSBsb2Ugc2VlIHNpc3NlLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiMjIFZla3RvcmlkDQoNCktlc2tuZSBvYmpla3RpdMO8w7xwIFItcyBvbiB2ZWt0b3IgKHNhcm5hbmUgMUQgYG51bXB5IGFycmF5YC1nYSkuIG1pcyBvbiB0ZWlzaXPDtW51IMO8aGUgbcO1w7V0bWVsaW5lIHNhbWEgdMO8w7xwaSBtdXV0dWphdGUgasOkcmplc3R1cy4gVmVrdG9yaXRlIGxvb21pc2VrcyBvbiBtaXRtZWlkIHZpaXNlLg0KDQpgYGB7cn0NCjE6MTAgIyBKw6RyamVzdCBudW1icmlkIA0KOToyICMgVGFndXJwaWRpIGrDpHJqZXN0dXMNCmMoMSwgNCwgMiwgNikgIyBTdXZhbGlzdGUgZWxlbWVudGlkZSBqw6RyamVzdHVzDQpjKDE6MTAsIDQsIGMoMiwgNCkpICMgQXJndW1lbmRpZCB2w7VpdmFkIG9sbGEgbmlpIHZla3Rvcmkga3VpIMO8a3Npa3VkIHbDpMOkcnR1c2VkDQpjKCJBIiwgIkIiLCAiQyIpICMgVmVrdG9yaXNzZSB2w7VpYiBwYW5uYSBrYSBzw7VuZXNpZA0Kc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMCkgIyBTdXZhbGlzZSBhbGd1cy0gamEgbMO1cHBwdW5rdGlnYSB2w7VyZHNlIHZhaGVtaWt1Z2EgamFkYWQNCnJlcCgxOjIsIHRpbWVzID0gNSkNCmBgYA0KDQpmbmMgImMiIHTDpGhlbmRhYiBjb21iaW5lISEhIGMoMSwgMikgbG9vYiAxZCBhcnJheSAodmVrdG9yaSkgWzEsIDJdDQoNCkFuZG1ldGUgZXJhbGRhbWluZSB2ZWt0b3Jpc3Qga8OkaWIga2FuZGlsaXN0ZSBzdWxndWRlZ2EuIFRhc3ViIG1lZWxkZSBqw6R0dGEgZXQgKipSLXMgYWxnYWIgaW5kZWtzZWVyaW1pbmUgMS1zdC4qKg0KDQpgYGB7cn0NCnggPSAxMDoxDQp4WzFdDQp4WzE6NV0NCmBgYA0KDQpUZWdlbGlrdWx0IGvDpHNpdGxldGFrc2UgUi1zIGlzZWdpIMO8aGUgdsOkw6RydHVzZWdhIG9iamVrdGUgdmVrdG9yaXRlbmEuIFNlZWdhIHBvbGUgc3V1cnQgdmFoZXQga2FzIHRlaGV0ZXMgb24gdmVrdG9yIHbDtWkgw7xrc2lrdWQgdsOkw6RydHVzZWQuIFZla3RvcmkgcHVodWwgdGVoYWtzZSB0ZWh0ZWlkIGVsZW1lbmRpIGthdXBhLiBLdWkgc2lzZW5kaWtzIG9uIGVyaW5ldmEgcGlra3VzZWdhIHZla3RvcmlkIHNpaXMgbMO8aGVtYXQga29ycmF0YWtzZSBuaWkga2F1YSBrdWkgc2FhYWIgcGlrZW1hZ2EgdsO1cmRzZWtzLiBTYW1hIGxvb2dpa2Ega2VodGliIG5paSBhcml0bWVldGlsaXN0ZSB0ZWhldGUga3VpIGthIHBhbGp1ZGUgZnVua3RzaW9vbmlkZSBwdWh1bC4NCg0KYGBge3J9DQoxOjEwICsgNSANCjE6MTAgKyAxMDoxDQpsb2coYygxLCAxMCwgMTAwLCAxMDAwKSwgYmFzZSA9IDEwKQ0KYm1pKGMoODUsIDkwLCA5NSwgMTAwLCAxMDUpLCAxOTQpDQpgYGANCg0KUGFsanVkZSBmdW5rdHNpb29uaWRlIHB1aHVsIG11aWR1Z2kgb29kYXRha3NlZ2kgdmVrdG9yaXQgamEgdHVsZW11c2VrcyBvbiBhcnYuDQoNCmBgYHtyfQ0KbWluKDE6MTApDQptYXgoMToxMCkNCm1lYW4oMToxMCkNCm1lZGlhbigwOjEwKQ0KYGBgDQoNCiMjIyDDnGxlc2FubmUgNA0KDQotICAgRGVmaW5lZXJpIGZ1bmt0c2lvb24gbWlzIHRlaXNlbmRhYiBldHRlYW50dWQgbnVtYnJpbGlzZSB2ZWt0b3JpIDAgamEgMSB2YWhlbGUsIG5paSBldCBtaW5pbWFhbG5lIHbDpMOkcnR1cyBvbiAwIGphIG1ha3NpbWFhbG5lIDEuIFZpaGplOiB2ZWt0b3JpICp4KiBqYSBzZWxsZSBlbGVtZW5kaSAqeGkqIGtvcnJhbCBvbiB2YWxlbSBqw6RyZ21pbmUgKih4aSAtIG1pbih4KSkvKG1heCh4KSAtbWluKHgpKS4qDQoNCi0gICBUZXN0aSBzYWFkdWQgZnVua3RzaW9vbmksIHZla3Rvcml0ZSBgMDoxMGAgamEgYC01OjVgIHBlYWwuIEthcyB0dWxlbXVzZWQgb24gc2FybmFzZWQgdsO1aSBlcmluZXZhZD8NCg0KYGBge3J9DQoNCnZla3Rvcl90ZWlzZW5kYSA9IGZ1bmN0aW9uKHZla3Rvcikgew0KICANCiAgdmFzdHVzID0odmVrdG9yIC0gbWluKHZla3RvcikpIC8gKG1heCh2ZWt0b3IpIC0gbWluKHZla3RvcikpDQogIA0KICByZXR1cm4odmFzdHVzKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KDQp2ZWt0b3JfdGVpc2VuZGEoMDoxMCkNCmBgYA0KDQojIyBBbmRtZXRhYmVsaWQNCg0KUHJha3RpbGlzZSBhbmRtZWFuYWzDvMO8c2kgcHVodWwgb24gYW5kbWVkIGFudHVkIHRhYmVsaXRlcywga3VzIHZlZXJndWRlcyBvbiBlcmluZXZhdCB0w7zDvHBpIHR1bm51c2VkLiBTw6TDpHJhbmUgdGFiZWwgb24gb2x1bGlzZWwga29oYWwga2EgUi1zIGt1cyBhamFsb29saXNlbHQgb24ga2FzdXRhdHVkIGBkYXRhLmZyYW1lYCBuaW1lbGlzdCBvYmpla3RpIGphIHZpaW1hc2VsIGFqYWwgcm9oa2VtIGB0aWJibGVgIG5pbWVsaXN0IGBkYXRhLmZyYW1lYCBlZGFzaWFyZW5kdXN0IGB0aWR5dmVyc2VgIHBha2V0aXN0LiBTZWxsaXNlc3QgdGFiZWxpc3QgdsO1aWIgbcO1ZWxkYSBrdWkgdHVubnVzdmVrdG9yaXRlIGtvZ3VtaXN0LCBrdXMga8O1aWsgdHVubnVzdmVrdG9yaWQgcGVhdmFkIG9sZW1hIMO8aGVwaWtrdXNlZC4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBwZWFiIHRlZ2VtYSB2YWlkIGtvcnJhIHNlc3Npb29uaSBhbGd1c2VzDQoNCm1vb2R1ZCA9IHRpYmJsZSgNCiAgTmltaSA9IGMoIk1hcmkiLCAiSmFhbmEiLCAiUGVldGVyIiksDQogIFBpa2t1cyA9IGMoMTU1LCAxNjUsIDE5MCksIA0KICBLYWFsID0gYyg1MCwgNjAsIDEwMCksIA0KICBTdWd1ID0gYygiTiIsICJOIiwgIk0iKQ0KKQ0KDQptb29kdWQNCmBgYA0KDQojIyBBbmRtZXRhYmVsaSBzaXNzZSBsdWdlbWluZSBlcmluZXZhdGVzdCBhbGxpa2F0ZXN0DQoNCiMjIyBUZWtzdGlmYWlsaWQNCg0KQW5kbWV0YWJlbGVpZCBob2l0YWtzZSBlbmFtYXN0aSB0ZWtzdGlmYWlsaWRlbmEsIGt1cyB2ZWVydWQgb24ga2FzIHRhYnVsYWF0b3JpLCBrb21hIHbDtWkgc2VtaWtvb2xvbmlnYSBlcmFsZGF0dWQuIFNlbGxpc3RlIGZhaWxpZGUgbHVnZW1pc2VrcyBzYWFiIGthc3V0YWRhIGByZWFkX2RlbGltYCBrw6Rza3UsIGt1cyB0dWxlYiBraW5kbGFzdGkgbcOkw6RyYXRhIGZhaWxpbWkgamEgdmVlcmd1ZGUgZXJhbGRhamEuDQoNCmBgYHtyfQ0KbWFzcyA9IHJlYWRfZGVsaW0oIm1hc3NhY2h1c2V0dHMuY3N2IiwgZGVsaW0gPSAiLCIpDQptYXNzDQpgYGANCg0KYGBge3J9DQptYXNzID0gcmVhZF9kZWxpbSgibWFzc2FjaHVzZXR0cy5jc3YiLCBkZWxpbSA9ICJcdCIpDQptYXNzDQpgYGANCg0KS3VuYSBlbmFtdXMgZmFpbGUgb24gZXJhbGRhdHVkIGthcyB0YWJ1bGFhdG9yaSB2w7VpIGtvbWFnYSwgc2lpcyBuZW5kZSBmYWlsaWRlIGphb2tzIG9uIGthIHNob3J0Y3V0aWQgYHJlYWRfdHN2YCBqYSBgcmVhZF9jc3ZgLg0KDQpgYGB7cn0NCm1hc3MgPSByZWFkX2NzdigibWFzc2FjaHVzZXR0cy5jc3YiKQ0KDQptYXNzDQpgYGANCg0KS2lyamVsZGF0dWQga8Okc3VkIHRlZXZhZCDDvGxkaXNlbHQgcMOkcmlzIGhlYWQgdMO2w7ZkLCBrdWlkIHNpaXNraSBvbiBmYWlsaWRlbCB0aWh0aSBvbWFww6RyYXNpZCwgbsOkaXRla3Mgb24gcHV1ZHV2YWQgdsOkw6RydHVzZWQgaW1lbGlrdWx0IG3DpHJnaXR1ZCwgbXV1dHVqYSBsb2V0YWtzZSBzaXNzZSB2YWxlIHTDvMO8YmlnYSwgam5lLiBOZW5kZSBwcm9ibGVlbWlkZWdhIG9uIHbDtWltYWxpayB0ZWdlbGVkYSBlbmFtYXN0aSBqdWJhIGByZWFkXypgIGvDpHN1IHNlZXMgcmFrZW5kYWRlcyBlcmluZXZhaWQgcGFyYW1lZXRyZWlkLiBUYXN1YiBsdWdlZGEgbmVuZGUgZnVua3RzaW9vbmlkIGFiaWxlaGVrw7xsZ2kgamEgb3RzaWRhIG7DpGl0ZWlkIGt1aSBow6R0dGEgasOkw6RiLg0KDQojIyMjIMOcbGVzYW5uZSA1DQoNCi0gICBMb2Ugc2lzc2UgYW5kbWVzdGlrIGZhaWxpc3QgbWFzc2FjaHVzZXR0cy50eHQgKHNlZSBhc3ViIHNhbWFzIGthdGFsb29naXMga3VpIHByYWt0aWt1bWkgbm90ZWJvb2spLg0KDQpgYGB7cn0NCg0KbWFzc190eHQgPSByZWFkX3RzdigibWFzc2FjaHVzZXR0cy50eHQiKQ0KDQptYXNzX3R4dA0KYGBgDQoNCiMjIyBSRGF0YQ0KDQpSIHRhYmVsaXRlIHNhbHZlc3RhbWluZSB0ZWtzdGlmYWlsaWRlbmEgbmluZyBuZWRlIGhpbGplbSBzaXNzZWx1Z2VtaW5lIHbDtWliIHDDtWhqdXN0YWRhIHZpZ3UuIEt1aSBtw7VuaSBmYWlsIGx1dWFrc2UgUi1zIGphIGhpbGplbSBrYSBSLXMgZWRhc2kgdMO2w7ZkZWxkYWtzZSBvbiBrYXN1bGlrIHRhIHNhbHZlc3RhZGEgYmluYWFyc2UgUiBvYmpla3RpbmEuIE5paSBvbiB0ZWRhIHbDpGdhIGxpaHRuZSBoaWxqZW0gc2lzc2UgbHVnZWRhLiBTZWRhIG9uIHbDtWltYWxpayBzYWF2dXRhZGEga8Okc2t1ZGVnYSBgc2F2ZWAgamEgYGxvYWRgLiBLw6RzdWxlIHNhdmUgdsO1aWIgZXR0ZSBhbmRhIGthIG1pdHUgb2JqZWt0aS4NCg0KYGBge3J9DQp4ID0gMQ0Kc2F2ZSh4LCBtYXNzLCBmaWxlID0gIm9iamVjdHMuUkRhdGEiKQ0KYGBgDQoNClNhbHZlc3RhdHVkIG9iamVrdGkgc2FhYiBzaXNzZSBsdWdlZGEga8Okc3VnYSBgbG9hZGAuIFZhaWtpbWlzaSB0ZWtpdmFkIHTDtsO2a2Vza2tvbmRhIHNhbWEgbmltZWdhIG9iamVrdGlkIG5hZ3Ugc2FpIHNhbHZlc3RhdHVkLiBFdCBhcnUgc2FhZGEgbWlzIHTDpHBzZWx0IHNpc3NlIGxvZXRpLCB0YXN1YiBrYXN1dGFkYSBwYXJhbWVldHJpdCBgdmVyYm9zZWAuDQoNCmBgYHtyfQ0KbG9hZCgib2JqZWN0cy5SRGF0YSIsIHZlcmJvc2UgPSBUKQ0KYGBgDQoNCiMjIEFuZG1ldGFiZWxpdGUgdMO2w7Z0bGVtaW5lIHRpZHl2ZXJzZSBrw6Rza3VkZWdhDQoNClItcyBvbiBtaXRtZWlkIHZpaXNlIGt1aWRhcyBhbmRtZWlkIHNhYWIgdMO2w7ZkZWxkYSwgdmlpbWFzZWwgYWphbCBvbiB2w6RnYSBwb3B1bGFhcnNla3Mgc2FhbnVkIGzDpGhlbmVtaW5lIG1pcyBvbiBpbXBsZW1lbnRlZXJpdHVkIHBha2V0dGlkZSBrb2d1bWlrdXMgw7xoaXNlIG5pbWV0YWphZ2EgYHRpZHl2ZXJzZWAuIFbDtWlrcyDDtmVsZGEsIGV0IHRlZ3Ugb24gbGF1c2Egb21hbW9vZGkgYWxhbWtlZWxlZ2EgUi1pIHNlZXMsIG1pcyBsYWVuYWIga29udHNlcHRzaW9vbmUgZXJpbnZlYXRlc3Qga2VlbHRlc3QgbmFndSBgU1FMYCBqYSBgYmFzaGAgbmluZyBwcm9vdmliIHBhbGp1ZCBvcGVyYXRzaW9vbmlkIHBhbm5hIHTDtsO2bGUgw7xoaXN0ZSBwcmludHNpaXBpZGUgcMO1aGphbC4NCg0KIyMjICVcPiUNCg0Kw5xoZWtzIHTDpGh0c2FtYWtzIGvDpHN1a3MgdGlkeXZlcnNlIHB1aHVsIG9uIG5uICJ0b3J1IiBgJT4lYCBtaXMgdsO1aW1hbGRhYiBraXJqdXRhZGEgcGlra2FzaWQga8Okc2t1ZGUgamFkYXNpZCBsb2V0YXZhbHQuIElsbHVzdHJlZXJpbWFrcyBzZWxsZSBrYXN1bGlra3VzdCB2YWF0YW1lIGrDpHJnbWlzdCBuw6RpZGV0Lg0KDQpgYGB7cn0NCnByaWNlcyA9IGMoIiQxNDIzLjU1IiwgIiQ1NTYuOTgiLCAiJDQzMjEuOTkiLCAiJDY1Ny4wMSIpDQoNCnByaWNlc190cmltID0gc3RyX3JlcGxhY2UocHJpY2VzLCAiXFwkIiwgIiIpDQpwcmljZXNfdHJpbV9udW0gPSBhcy5udW1lcmljKHByaWNlc190cmltKSANCnByaWNlc190cmltX251bV9yb3VuZCA9IHJvdW5kKHByaWNlc190cmltX251bSwgZGlnaXRzID0gMCkNCnByaWNlc19yb3VuZF9maW5hbCA9IHN0cl9jKCIkIiwgcHJpY2VzX3RyaW1fbnVtX3JvdW5kKQ0KcHJpY2VzX3JvdW5kX2ZpbmFsDQpgYGANCg0KU2lpbiByYWtlbmRhbWUgasOkcmplc3QgbWl0bWVpZCBvcGVyYXRzaW9vbmUgamEgc2FsdmVzdGFtZSBrw7VpayB2YWhlbXV1dHVqYXRlc3NlIG1pbGxlbGUgcGVhbWUgbmltZXNpZCBtw7V0bGVtYSwgbWlzIG9uIHN1aHRlbGlzZWx0IHTDvMO8dHUuIE1lIHbDtWltZSBzZWxsaXNlIG9wZXJhdHNpb29vbmkga2lyanV0YWRhIGthIMO8aGVsIHJlYWwuDQoNCmBgYHtyfQ0Kc3RyX2MoIiQiLCByb3VuZChhcy5udW1lcmljKHN0cl9yZXBsYWNlKHByaWNlcywgIlxcJCIsICIiKSksIGRpZ2l0cyA9IDApKQ0KYGBgDQoNCkt1aWQgc2VsbGlzdCBhc2phIG9uIHbDpGdhIHJhc2tlIGx1Z2VkYS4gU2VzdCBmdW5rdHNpb29uZSByYWtlbmRhdGFrc2UgasOkcmplc3Qgc2Vlc3Rwb29vbHQgdsOkbGphcG9vbGUuIFBhbGp1IGxpaHRzYW0gb2xla3MgbHVnZWRhLCBrdWkgZnVua3RzaW9vbmlkIGxpaWd1a3MgcmFrZW5kYW1pc2UgasOkcmpla29ycmFzIHZhc2FrdWx0IHBhcmVtYWxlLiBTZWUgb24ganVzdCBzZWUgbWlkYSBvcGVyYWF0b3IgYCU+JWAgdGVlYi4gVGEgdsO1aW1hbGRhYiBraXJqdXRhZGEgYGYoZyh4KSlgLCBrdWkgYHggJT4lIGcoKSAlPiUgZigpYC4gTWVpZSBlZWxtaW5lIG7DpGlkZSBuw6Rla3MgdsOkbGphIGrDpHJnbmV2Lg0KDQpgYGB7cn0NCnByaWNlcyAlPiUgc3RyX3JlcGxhY2UoIlxcJCIsICIiKSAlPiUgYXMubnVtZXJpYygpICU+JSByb3VuZCgpICU+JSBzdHJfYygiJCIsIC4pDQpgYGANCg0KUGFuZSB0w6RoZWxlLCBldCBrdWkgZWVsbmV2YSBmdW5rdHNpb29uaSB2w6RsanVuZCBwZWFrcyBvbGVtYSBqw6RyZ21pc2VzIGZ1bmt0c2lvb25pIHbDpGxqYWt1dHNlcyBlc2ltZXNlbCBwb3NpdHNpb29uaWwsIHNpaXMgdsO1aWIgc2VsbGUgw6RyYSBqw6R0dGEuIEt1aSB2w6RsanVuZCBwZWFiIG1pbmVtYSBtw7VuZWxlIHRlaXNlbGUgcG9zaXRzaW9vbmlsZSwgc2lpcyBzYWFiIHNlZGEgbcOkcmtpZGEga3VpIGAuYCAodnQgdmlpbWFuZSBrw6RzaykuDQoNCiMjIyBUaWR5dmVyc2UgZnVua3RzaW9vbmlkDQoNClRpZHl2ZXJzZSBvbiDDvGxlcyBlaGl0YXR1ZCBmdW5rdHNpb29uaWRlbCwgbWlsbGVzdCBpZ2HDvGtzIHbDtXRhYiBzaXNzZSBhbmRtZXRhYmVsaSBqYSBhbm5hYiB2w6RsamEgbW9kaWZpdHNlZXJpdHVkIGFuZG1ldGFiZWxpLiBJZ2EgZnVua3RzaW9vbiB0ZW9zdGFiIGFpbnVsdCDDvGh0ZSBvcGVyYXRzaW9vbmksIGFnYSBrdWkgbmVpZCBvcGVyYWF0b3JpZ2EgJVw+JSBqw6RyamVzdCByYWtlbmRhZGEgb24gdsO1aW1hbGlrIHNhYXZ1dGFkYSB2w6RnYSBwYWxqdS4gUGVhbWlzZWQgZnVua3RzaW9vbmlkIHRpZHl2ZXJzZSBwYWtldHRpZGVzIG9uIGrDpHJnbmV2YWQ6DQoNCi0gICAqKnNlbGVjdCoqIC0gdsO1aW1hbGRhYiB2YWxpZGEgYW5kbWV0YWJlbGkgdmVlcmdlDQoNCi0gICAqKmZpbHRlciAtKiogdsO1aW1hbGRhYiB2YWxpZGEgYW5kbWV0YWJlbGkgcmlkdQ0KDQotICAgKiptdXRhdGUqKiAtIHbDtWltYWxkYWIgdGVraXRhZGEgdGFiZWxpc3NlIHV1c2kgdmVlcmd1c2lkIHbDtWkgbW9kaWZpdHNlZXJpZGEgdmFudQ0KDQotICAgKipncm91cF9ieSoqIGphICoqc3VtbWFyaXplKiogLSB2w7VpbWFsZGF2YWQgc3VtbWVlcmlkYSB2w6TDpHJ0dXNpIHR1bm51c3RlIHBvb2x0IGRlZmluZWVyaXR1ZCBncnVwcGlkZXMNCg0KLSAgICoqYXJyYW5nZSoqIC0gdsO1aW1hbGRhYiBzb3J0ZWVyaWRhIHRhYmVsaXQgw7xoZSB2w7VpIG1pdG1lIHR1bm51c2UgasOkcmdpDQoNCkrDpHJnbmV2YWx0IHZhYXRhbWUgaWdhIGZ1bmt0c2lvb25pIGphIG3DtW5kYSBrYXN1dHVzbsOkaWRldCB0w6Rwc2VtYWx0IGthc3V0YWRlcyBhbmRtZXN0aWtrdSBgbWFzc2AsIG1pcyBzYWkgc2lzc2UgbG9ldHVkIGVlbG5ldmFsdC4NCg0KIyMjIyBzZWxlY3QNCg0KYHNlbGVjdGAgdsO1aW1hbGRhYiBhbmRtZXRhYmVsaSB2ZWVyZ2UgdmFsaWRhIGphIG5laWQga2Egc2VsbGUga8OkaWd1cyDDvG1iZXIgbmltZXRhZGEuIFZlZXJndWRlIG5pbWVkIHNhYWIgZnVua3RzaW9vbmlsZSBldHRlIGFuZGEgaWxtYSBqdXR1bcOkcmtpZGV0YS4NCg0KYGBge3J9DQptYXNzICU+JSBzZWxlY3QoQUdFUCwgU0VYKQ0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgc2VsZWN0KEFnZSA9IEFHRVAsIEdlbmRlciA9IFNFWCkgIyBwYW5lbWUgaWx1c2FtYWQgbmltZWQNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIHNlbGVjdCgtQ0lULCAtQUdFUCkgIyBTYWFtZSBrYSBrb25rcmVldHNlaWQgdmVlcmdlIHbDpGxqYSB2aXNhdGENCmBgYA0KDQojIyMjIGZpbHRlcg0KDQpgZmlsdGVyYCB2w7VpbWFsZGFiIHJpZHUgZmlsdHJlZXJpZGEgc2VhZGVzIGxvb2dpbGlzaSB0aW5naW11c2kgdmVlcmd1ZGVsZS4gU2lzZW5kdGFiZWxpcyBvbGV2YXRlIHZlZXJndWRlIG5pbWVkIHR1bm5lYiBgZmlsdGVyYCBhdXRvbWFhdHNlbHQgw6RyYS4NCg0KYGBge3J9DQptYXNzICU+JSBmaWx0ZXIoQUdFUCA8IDIwKSAjIFZhbnVzIHbDpGlrc2VtIGt1aSAyMA0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgZmlsdGVyKENJVCA9PSAiTm90IGEgY2l0aXplbiBvZiB0aGUgVS5TLiIpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBmaWx0ZXIoDQogIENJVCANCiAgICAlaW4lIA0KICAgICAgYygNCiAgICAgICAgIlUuUy4gY2l0aXplbiBieSBuYXR1cmFsaXphdGlvbiIsIA0KICAgICAgICAiTm90IGEgY2l0aXplbiBvZiB0aGUgVS5TLiINCiAgICAgICkNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgZmlsdGVyKEFHRVAgPCAyMCB8IENJVCA9PSAiTm90IGEgY2l0aXplbiBvZiB0aGUgVS5TLiIpIA0KYGBgDQoNCiMjIyMgbXV0YXRlDQoNCmBtdXRhdGVgIHbDtWltYWxkYWIgbHV1YSB1dXNpIHZlZXJhaWQgdmFzdGF2YWx0IHNlbGxlbGUga2FzIHZlZXJnIG1pbGxlbGUgdsOkw6RydHVzIG9taXN0YXRha3NlIGp1YmEgZWtzaXRlZXJpYiB2w7VpIG1pdHRlLiBOYWd1IGVlbG5ldmF0ZWxnaSBmdW5rdHNpb2dlIHbDtWkgbXV1dGEgb2xlbWFzb2xldm9uaWRlbCBgbXV0YXRlYCBzZWVzIHNhYWIgdGVoZXRlbCBrYXN1dGFkYSB2ZWVydW5pbWVzaWQuDQoNCmBgYHtyfQ0KbWFzcyAlPiUgbXV0YXRlKE9sZCA9IEFHRVAgPiA2MCkgJT4lIHNlbGVjdChBR0VQLCBPbGQpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBtdXRhdGUoQUdFUCA9IEFHRVAgKyAxKSANCmBgYA0KDQojIyMjIHN1bW1hcml6ZQ0KDQpgc3VtbWFyaXplYCBvbiBrw6RzayBtaXMgdsO1aW1hbGRhYiBhbmRtZXN0aWt1bCBrb2trdXbDtXR0ZWlkIHbDpGxqYSBhcnZ1dGFkYS4gRXJpbmV2YWx0IGBtdXRhdGVgIGvDpHN1c3QgdGFnYXN0YWIgdGEgYWludWx0IHbDpGxqYWFydnV0YXR1ZCBzdXVydXNlZCBqYSBtaXR0ZSBtaWRhZ2kgbXV1ZC4NCg0KYGBge3J9DQptYXNzICU+JSBzdW1tYXJpemUoTWVhbkFnZSA9IG1lYW4oQUdFUCkpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBzdW1tYXJpemUoTWVhbkFnZSA9IG1lYW4oQUdFUCksIE1pbkFnZSA9IG1pbihBR0VQKSkNCmBgYA0KDQpGdW5rdHNpb29uIGBuKClgIGBzdW1tYXJpemVgIHNlZXMgw7x0bGViIGt1aSBtaXR1IHJpZGEgc2lzZW5kIHRhYmVsaXMgb24uDQoNCmBgYHtyfQ0KbWFzcyAlPiUgc3VtbWFyaXplKE1lYW5BZ2UgPSBtZWFuKEFHRVApLCBNaW5BZ2UgPSBtaW4oQUdFUCksIE4gPSBuKCkpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSANCiAgc3VtbWFyaXplKA0KICAgIE1lYW5BZ2UgPSBtZWFuKEFHRVApLCANCiAgICBNaW5BZ2UgPSBtaW4oQUdFUCksIA0KICAgIE1heEFnZSA9IG1heChBR0VQKSwgDQogICAgTiA9IG4oKQ0KICApOw0KYGBgDQoNCiMjIyMgZ3JvdXBfYnkNCg0KYGdyb3VwX2J5YCB2w7VpbWFsZGFiIHJha2VuZGFkYSBubiAiKnNwbGl0LWFwcGx5LWNvbWJpbmUqInN0cmF0ZWVnaWF0LCBrdXMgYW5kbWVzdGlrIHTDvGtlbGRhdGFrc2Ugw7xoZSB2w7VpIG1pdG1lIHR1bm51c2UgdsOkw6RydHVzIGFsdXNlbCBhbGFtYW5kbWVzdGlrZWtzLCByYWtlbmRhdGFrc2UgbWluZ2l0IGZ1bmt0c2lvb25pIGFsYW0tYW5kbWVzdGlrZWwgbmluZyB0dWxlbXVzZWQga29tYmluZWVyaXRha3NlLiBLdWkgbWUgb2xlbWUgcmFrZW5kYW51ZCBrw6Rza3UgZ3JvdXBfYnkgYW5kbWVzdGlrdWxlIHNpaXMgasOkcmduZXZhdGUga8Okc2t1ZGUgcHVodWwganVzdCBzZWxsaW5lIG9wZXJhdHNpb29uIHRvaW11YmtpLg0KDQpgZ3JvdXBfYnlgIGphIGBzdW1tYXJpemVgIG1vb2R1c3RhdmFkIGtvbWJpbmF0c2lvb25pLCBtaWxsZWdhIG9uIHbDtWltYWxpayB0dW5udXN0ZSBwb29sdCBkZWZpbmVlcml0dWQgZ3J1cHBpZGUga2F1cGEgc3VtbWVlcmlkYSB0ZWlzdGUgbXV1dHVqYXRlIHbDpMOkcnR1c2VpZC4gVHVsZW11c3NlIGrDpMOkdmFkIGFsbGVzIHR1bm51c2VkIG1pbGxlIGrDpHJnaSBncnVwZWVyaXRpIG5pbmcgYHN1bW1hcml6ZWAga8Okc3VzIGRlZmluZWVlcml0dWQgdHVubnVzZWQuIEdydXBlZXJpZGEgdsO1aWIgbmlpIMO8aGUga3VpIG1pdG1lIHR1bm51c2UgasOkcmdpLg0KDQpgYGB7cn0NCm1hc3MgJT4lIGdyb3VwX2J5KENJVCkgJT4lIHN1bW1hcml6ZShBR0VQID0gbWVhbihBR0VQKSkNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIGdyb3VwX2J5KENJVCwgU0VYKSAlPiUgc3VtbWFyaXplKEFHRVAgPSBtZWFuKEFHRVApKQ0KYGBgDQoNCkZ1bmt0c2lvb24gYG4oKWAgYHN1bW1hcml6ZWAgc2VlcyDDvHRsZWIga3VpIG1pdHUgdGFiZWxpIHJpZGEga29ua3JlZXRzZWxlIGdydXBlZXJpdmF0ZSB0dW5udXN0ZSBrb21iaW5hdHNpb29uaWxlIHZhc3RhYi4gU2VlIG9uIHbDpGdhIGthc3VsaWsgc2FnZWR1c3RhYmVsaXRlIHRlZ2VtaXNlbA0KDQpgYGB7cn0NCm1hc3MgJT4lIGdyb3VwX2J5KENJVCkgJT4lIHN1bW1hcml6ZShOID0gbigpKQ0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgZ3JvdXBfYnkoQ0lULCBTRVgpICU+JSBzdW1tYXJpemUoQUdFUCA9IG1lYW4oQUdFUCksIE4gPSBuKCkpDQpgYGANCg0KS3VpIHDDpHJhc3QgZ3JvdXBfYnkga8Okc2t1IHJha2VuZGEgbXV0YXRlIGvDpHNrdS4gU2lpcyBtdXRhdGUgdMO2w7Z0YWIganVwaWthdXBhIGdyb3VwX2J5IGRlZmluZWVyaXR1ZCBhbGFtaHVsa2FkZWwuIE7DpGl0ZWtzIG5paSBzYWFtZSBsaXNhZGEgaWdhbGUgcmVhbGUgZ3J1cGlrZXNrbWlzZSB2w7VpIGrDpHJqZWtvcnJhIG51bWJyaSBncnVwaSBzZWVzLg0KDQpgYGB7cn0NCm1hc3MgJT4lIHNlbGVjdChBR0VQLCBTRVgpICU+JSBncm91cF9ieShTRVgpICU+JSBtdXRhdGUoTWVhbkFnZUdyb3VwID0gbWVhbihBR0VQKSkNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIHNlbGVjdChBR0VQLCBTRVgpICU+JSBncm91cF9ieShTRVgpICU+JSBtdXRhdGUoSWRJbkdyb3VwID0gMTpuKCkpDQpgYGANCg0KIyMjIyBhcnJhbmdlDQoNCmBhcnJhbmdlYCBsaWh0c2FsdCBzb3J0ZWVyaWIgYW5kbWV0YWJlbGkgZXR0ZWFudHVkIHR1bm51cyh0KWUgasOkcmdpLg0KDQpgYGB7cn0NCm1hc3MgJT4lIGFycmFuZ2UoQUdFUCkgIyBWYWlraW1pc2UgdsOkaWtzZW1hc3Qgc3V1cmVtYWtzDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBzZWxlY3QoQUdFUCwgU0VYKSAlPiUgYXJyYW5nZShBR0VQLCBTRVgpDQpgYGANCg0KYGBge3J9DQojIG1hc3MgJT4lIGFycmFuZ2UoZGVzYyhBR0VQKSkgIyBLw6RzdWdhIGRlc2Mgc2FhYiBzb3J0ZWVyaW1pc2Ugc3V1bmEgw7xtYmVyIHDDtsO2cmF0YQ0KDQptYXNzICU+JSBhcnJhbmdlKC1BR0VQKSAjIEvDpHN1Z2EgZGVzYyBzYWFiIHNvcnRlZXJpbWlzZSBzdXVuYSDDvG1iZXIgcMO2w7ZyYXRhDQpgYGANCg0KIyMjIEZ1bmt0c2lvb25pZGUga29tYmluZWVyaW1pbmUNCg0KT2xndWdpLCBldCBpZ2EgZnVua3RzaW9vbiDDvGtzaSB0ZW9zdGFiIGtvbmtyZWV0c2UgbGlodHNhIG9wZXJhdHNpb29uaSwgdsO1aWIgbmVpZCDDvGtzdGVpc2UgasOkcmVsIHJpdHRhIGxhZHVkZXMgbGFoZW5kYWRhIHDDpHJpcyBrZWVydWthaWQgcHJvYmxlZW1lLiBOw6RpdGVrcyBzaWluIGxlaWFtZSB0b3AgNSBhbWV0aXQgdMO2w7ZlYWxpc3RlbGUgVVNBcyBqYSB2w6RsamFzcG9vbCBzw7xuZGludWQgaW5pbWVzdGVsZS4NCg0KYGBge3J9DQptYXNzICU+JSANCiAgc2VsZWN0KEFnZSA9IEFHRVAsIENpdGl6ZW5zaGlwID0gQ0lULCBPY2N1cGF0aW9uID0gT0NDUCkgJT4lIA0KICBmaWx0ZXIoQWdlID4gMTggJiBBZ2UgPCA2NSkgJT4lIA0KICBtdXRhdGUoQm9yblVTID0gQ2l0aXplbnNoaXAgPT0gIkJvcm4gaW4gdGhlIFUuUy4iKSAlPiUgDQogIGdyb3VwX2J5KEJvcm5VUywgT2NjdXBhdGlvbikgJT4lIA0KICBzdW1tYXJpemUoTiA9IG4oKSkgJT4lIA0KICBncm91cF9ieShCb3JuVVMpICU+JSANCiAgbXV0YXRlKFJhbmsgPSByYW5rKC1OKSkgJT4lIA0KICBmaWx0ZXIoUmFuayA8PSA1KSAlPiUgDQogIGFycmFuZ2UoQm9yblVTLCBSYW5rKQ0KYGBgDQoNCiMjIyDDnGxlc2FuZGVkIDYNCg0KLSAgIEt1bW1hbCBvbiBrw7VyZ2VtIGtlc2ttaW5lIHBhbGsga2FzIMO8bGUgdsO1aSBhbGxhIDU1IGFhc3Rhc3RlbCBhcnN0aWRlbD8gKEthc3V0YSB0dW5udXNlaWQgV0FHUCAtIHBhbGssIEFHRVAgLSB2YW51cywgT0NDUCAtIGFtZXQsIGthdGVnb29yaWEgIk1FRC1QSFlTSUNJQU5TIEFORCBTVVJHRU9OUyIsIFcpDQoNCi0gICBNaWxsaXNlIGFtZXRlaWQgZXNpbmRhdmFkIG5haXNlZCB0w7bDtnRhdmFkIGtlc2ttaXNlbHQgbsOkZGFsYXMga8O1aWdlIGthdWVtPyBLZXNrZW5kdSBhaW51bHQgYW1ldGl0ZWxlLCBtaWRhIGVzaW5kYXZhaWQgbmFpc2kgb24gYW5kbWVzdGlrdXMgdsOkaGVtYWwgMTAuIChLYXN1dGEgdHVubnVzZWlkIEFHRVAgLSB2YW51cywgU0VYIC0gc3VndSB2w6TDpHJ0dXMgIkZlbWFsZSIgLCBXS0hQIC0gbsOkZGFsYXMgdGVodHVkIHTDtsO2dHVubmlkLCBPQ0NQIC0gYW1ldCkNCg0KLSAgIE1pcyBvbiB0w6Rpc2thc3ZhbnVkIGluaW1lc3RlIGvDtWlnZSBrw7VyZ2VtYSBrZXNrbWlzZSBwYWxnYWdhIGFtZXRpZCBNYXNzYWNodXNldHRzaSBhbmRtZXN0aWt1IGtvaGFzZWx0PyBLZXNrZW5kdSBhaW51bHQgYW1ldGl0ZWxlLCBtaWxsZSBlc2luZGFqYWlkIG9uIGFuZG1lc3Rpa3VzIHbDpGhlbWFsIDEwLiAoS2FzdXRhIHR1bm51c2VpZCBBR0VQIC0gdmFudXMsIFdBR1AgLSBwYWxrLCBPQ0NQIC0gYW1ldCkNCg0KYGBge3J9DQoNCm1hc3MgJT4lIA0KICBzZWxlY3QoQWdlID0gQUdFUCwgT2NjdXBhdGlvbiA9IE9DQ1AsIFdhZ2UgPSBXQUdQKSAlPiUgDQogIGZpbHRlcihPY2N1cGF0aW9uID09ICJNRUQtUEhZU0lDSUFOUyBBTkQgU1VSR0VPTlMiKSAlPiUgDQogIG11dGF0ZShpc092ZXI1NSA9IEFnZSA+PSA1NSkgJT4lIA0KICBncm91cF9ieShpc092ZXI1NSwgT2NjdXBhdGlvbikgJT4lIA0KICBzdW1tYXJpc2UoTWVhbldhZ2UgPSBtZWFuKFdhZ2UpKQ0KYGBgDQoNCmBgYHtyfQ0KDQptYXNzICU+JSANCiAgc2VsZWN0KHNleCA9IFNFWCwgb2NjdXBhdGlvbiA9IE9DQ1AsIHdvcmtIb3Vyc1BlcldlZWsgPSBXS0hQKSAlPiUNCiAgZmlsdGVyKHNleCA9PSAiRmVtYWxlIikgJT4lIA0KICBncm91cF9ieShvY2N1cGF0aW9uKSAlPiUNCiAgZmlsdGVyKG4oKSA+PSAxMCkgJT4lDQogIHN1bW1hcmlzZShtZWFuSG91cnMgPSBtZWFuKHdvcmtIb3Vyc1BlcldlZWspKSAlPiUgIyBtZWFuKHdvcmtIb3Vyc1BlcldlZWssIG5hLnJtID0gVFJVRSkNCiAgYXJyYW5nZSgtbWVhbkhvdXJzKSAlPiUNCiAgc2VsZWN0KG9jY3VwYXRpb24sIG1lYW5Ib3VycykNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIA0KICBmaWx0ZXIoU0VYID09ICJGZW1hbGUiKSAlPiUgDQogIGdyb3VwX2J5KE9DQ1ApICU+JSANCiAgZmlsdGVyKG4oKSA+PSAxMCkgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgbWVhbl9ob3VycyA9IG1lYW4oV0tIUCwgbmEucm0gPSBUUlVFKSwNCiAgICBjb3VudCA9IG4oKQ0KICApICU+JSANCiAgYXJyYW5nZShkZXNjKG1lYW5faG91cnMpKQ0KYGBgDQoNCiMgTGlzYW1hdGVyamFsZQ0KDQotICAgUHl0aG9uaSBzeW50YWtzaSB2w7VyZGx1cyBSLWdhOiA8aHR0cHM6Ly9wYW5kYXMucHlkYXRhLm9yZy9wYW5kYXMtZG9jcy9zdGFibGUvZ2V0dGluZ19zdGFydGVkL2NvbXBhcmlzb24vY29tcGFyaXNvbl93aXRoX3IuaHRtbD4NCg0KLSAgIFIgdHV0b3JpYWwgQW5kbWVrYWV2ZSBrdXJzdXNlc3Q6IDxodHRwczovL2NvdXJzZXMuY3MudXQuZWUvMjAxNy9ETS9mYWxsL01haW4vUlR1dG9yaWFsPg0K